﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Security.Principal;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.Text;
using Nvigorate.Logging;

namespace Nvigorate.WCF
{
    public class ServiceClient<TContract, TBinding> : ServiceClient<TContract>
        where TContract : class
        where TBinding : Binding
    {
        #region Fieldss

        protected TContract _channelInstance;
        protected ClientShell<TContract> _proxy;
        protected string _configuration = "";
        protected new TBinding _binding;
        protected EndpointAddress _endpoint;

        #endregion

        public TBinding Binding
        {
            get { return _binding; }
        }

        #region Public Methods

        public void Call(Action<TContract> call)
        {
            try
            {
                call(Proxy);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
            finally
            {
                Close();
            }
        }

        public TResult Call<TResult>(Func<TContract, TResult> call)
        {
            TResult result = default(TResult);
            try
            {
                result = call(Proxy);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
            finally
            {
                Close();
            }
            return result;
        }

        public virtual ServiceClient<TContract> AllTimeouts(TimeSpan time)
        {
            _binding.CloseTimeout = time;
            _binding.SendTimeout = time;
            _binding.ReceiveTimeout = time;
            _binding.OpenTimeout = time;
            return this;
        }

        public virtual ServiceClient<TContract> AllTimeouts(TimeSpan closeTimeout, TimeSpan openTimeout, TimeSpan receiveTimeout, TimeSpan sendTimeout)
        {
            _binding.CloseTimeout = closeTimeout;
            _binding.SendTimeout = sendTimeout;
            _binding.ReceiveTimeout = receiveTimeout;
            _binding.OpenTimeout = openTimeout;
            return this;
        }

        #endregion

        private void ReadServiceInfoFromMetadataExchange(string mexAddress)
        {
            var serviceInfo = MetadataResolver.Resolve(typeof(TContract), new Uri(mexAddress),
                                                       MetadataExchangeClientMode.HttpGet);

            var info = serviceInfo.FirstOrDefault(i => i.Binding.GetType().Equals(typeof(TBinding)));
            if (info == null)
                throw new ServiceClientException(string.Format("No service endpoints defined for {0}", mexAddress));

            _endpoint = info.Address;
            _binding = (TBinding)info.Binding;
        }

        #region Constructors

        protected ServiceClient()
        {

        }

        public ServiceClient(string metadataEndPoint)
        {
            ReadServiceInfoFromMetadataExchange(metadataEndPoint);
        }

        public ServiceClient(string remoteAddress, TBinding binding)
        {
            _binding = binding;
            _endpoint = new EndpointAddress(remoteAddress);
        }

        #endregion

        public static new ServiceClient<TContract, TBinding> FromConfigruation(string endPointConfiguration)
        {
            return new ServiceClient<TContract, TBinding>() { _configuration = endPointConfiguration };
        }

        public static new ServiceClient<TContract, TBinding> FromConfigruation()
        {
            return FromConfigruation(ServiceClientProvider.GetEndPointForContractType(typeof(TContract)));
        }

        #region Implementation of IDisposable

        public void Dispose()
        {
            Close();
        }

        protected void Close()
        {
            if (_proxy != null)
            {
                switch (_proxy.ChannelFactory.State)
                {
                    case CommunicationState.Faulted:
                        _proxy.ChannelFactory.Abort();
                        _proxy.Abort();
                        _proxy = null;
                        _channelInstance = null;
                        break;
                    default:
                        _proxy.ChannelFactory.Close();
                        _proxy.Close();
                        _proxy = null;
                        _channelInstance = null;
                        break;
                }
            }
        }

        #endregion
    }

    public class ServiceClient<TContract> : IDisposable
        where TContract : class
    {
        #region Fields

        protected TContract _channelInstance;
        protected ClientShell<TContract> _proxy;
        protected string _configuration = "";
        protected Binding _binding;
        protected EndpointAddress _endpoint;

        #endregion


        public TContract Proxy
        {
            get
            {
                if (_channelInstance == null)
                {
                    InstantiateChannel();
                }
                return _channelInstance;
            }
        }

        #region Public Methods

        public void Call(Action<TContract> call)
        {
            try
            {
                call(Proxy);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
            finally
            {
                Close();
            }
        }

        public TResult Call<TResult>(Func<TContract, TResult> call)
        {
            TResult result = default(TResult);
            try
            {
                result = call(Proxy);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
            finally
            {
                Close();
            }
            return result;
        }

        public virtual ServiceClient<TContract> AllTimeouts(TimeSpan time)
        {
            _binding.CloseTimeout = time;
            _binding.SendTimeout = time;
            _binding.ReceiveTimeout = time;
            _binding.OpenTimeout = time;
            return this;
        }

        public virtual ServiceClient<TContract> AllTimeouts(TimeSpan closeTimeout, TimeSpan openTimeout, TimeSpan receiveTimeout, TimeSpan sendTimeout)
        {
            _binding.CloseTimeout = closeTimeout;
            _binding.SendTimeout = sendTimeout;
            _binding.ReceiveTimeout = receiveTimeout;
            _binding.OpenTimeout = openTimeout;
            return this;
        }

        #endregion

        private void ReadServiceInfoFromMetadataExchange(string mexAddress)
        {
            var serviceInfo = MetadataResolver.Resolve(typeof(TContract), new Uri(mexAddress),
                                                       MetadataExchangeClientMode.HttpGet);

            if (serviceInfo.Count == 0)
                throw new ServiceClientException(string.Format("No service endpoints defined for {0}", mexAddress));

            var serviceEndPoint = serviceInfo.First();
            _endpoint = serviceEndPoint.Address;
            _binding = serviceEndPoint.Binding;
        }

        protected virtual ClientShell<TContract> GetProxyInstance()
        {
            return _configuration != "" ?
                            new ClientShell<TContract>(_configuration) :
                            new ClientShell<TContract>(_binding, _endpoint);
        }

        protected virtual void InstantiateChannel()
        {
            try
            {
                _proxy = GetProxyInstance();
                _channelInstance = _proxy.ChannelFactory.CreateChannel();
            }
            catch (Exception e)
            {
                LogManager.LogError(e, Severities.High);
                throw;
            }
        }

        #region Constructors

        protected ServiceClient()
        {
            _configuration = ServiceClientProvider.GetEndPointForContractType(typeof (TContract));
        }

        public ServiceClient(string metadataEndPoint)
        {
            ReadServiceInfoFromMetadataExchange(metadataEndPoint);
        }

        public ServiceClient(string remoteAddress, Binding binding)
        {
            _binding = binding;
            _endpoint = new EndpointAddress(remoteAddress);
        }

        #endregion

        public static ServiceClient<TContract> CreateFromConfigruation(string endPointConfiguration)
        {
            return new ServiceClient<TContract>() { _configuration = endPointConfiguration };
        }

        public static ServiceClient<TContract> CreateFromConfigruation()
        {
            return CreateFromConfigruation(ServiceClientProvider.GetEndPointForContractType(typeof(TContract)));
        }

        #region Implementation of IDisposable

        public void Dispose()
        {
            Close();
        }

        protected void Close()
        {
            if (_proxy != null)
            {
                switch (_proxy.ChannelFactory.State)
                {
                    case CommunicationState.Faulted:
                        _proxy.ChannelFactory.Abort();
                        _proxy.Abort();
                        _proxy = null;
                        _channelInstance = null;
                        break;
                    default:
                        try
                        {
                            _proxy.ChannelFactory.Close();
                            _proxy.Close();
                            _proxy = null;
                            _channelInstance = null;
                        }
                        catch (Exception e)
                        {
                            _proxy.ChannelFactory.Abort();
                            _proxy.Abort();
                            _proxy = null;
                            _channelInstance = null;
                        }
                        break;
                }
            }
        }

        #endregion
    }
}
